class Compile {
  constructor(node, vm) {
    this.vm = vm
    this.el = document.querySelector(node)
    // 1. 先把这些真实DOM移入到内存中fragment
    let fragment = this.nodeToFragment(this.el)
    // 2. 把内存的fragment, 编译 => 提取想要的元素节点
    this.compile(fragment)
    // 3. 把编译好的fragment塞到页面中
    this.el.appendChild(fragment)
  }

  nodeToFragment(node) {
    let fragment = document.createDocumentFragment()
    var child
    while (child = node.firstChild) {
      fragment.appendChild(child)
    }
    return fragment
  }

  compile(fragment) {
    // 当前父节点节点的子节点，包含文本节点，类数组对象
    let childNodes = fragment.childNodes
    Array.from(childNodes).forEach(node => {
      // node 节点有元素节点/文本节点, 需要区分2者进行
      if (this.isElementNode(node)) { // 元素节点
        // 编译元素
        this.compileElement(node, this.vm) // VM过程
        // 如果是元素节点，这可能是嵌套内容，所以要在遍历一遍，如 ul > li
        this.compile(node)
      } else { // 文本节点
        this.compileText(node, this.vm) // MV过程
      }
    })
  }

  isElementNode(node) {
    return node.nodeType == 1
  }

  compileElement(node, vm) {
    let attr = node.attributes
    Array.from(attr).forEach((attr, i) => {
      if (attr.name == 'v-model') {
        node.removeAttribute('v-model')
        // node.value = this.getValue(v.value) // 直接赋值
        let [, type] = attr.name.split('-')// 解构赋值[v,model]
        CompileUtil[type](node, this.vm, attr.value)

      } else if (attr.name == 'v-click') {
        let methodName = attr.value
        node.addEventListener('click', function () {
          return vm.$methods[methodName].bind(vm.$data)()
        })
      }

    })
  }

  compileText(node, vm) {
    // let reg = /\{\{(.)*\}\}/g
    let str = node.textContent
    let reg = /\{\{([^}]+)\}\}/g
    if (reg.test(node.textContent)) {
      CompileUtil['text'](node, this.vm, str)
    }
  }
}

CompileUtil = {
  model(node, vm, key) {
    let updateFn = this.updater['modelUpdater']
    new Watcher(vm, key, () => {
      updateFn && updateFn(node, this.getVal(vm, key))
    })
    // 通过观察者进行赋值
    // 为每个元素节点添加监听事件，如果值变化，则动态更改data中对应的对象的值。
    node.addEventListener('input', e => {
      let newValue = e.target.value
      this.setValue(vm, key, newValue) // view -> model 即 VM模式
    })
    updateFn && updateFn(node, this.getVal(vm, key))
  },
  text(node, vm, str) {
    // str是一个缓存字符串，保留原始的字符串结构，如 ‘我的名字是：{{name}}’, 后面会针对{{}}内容进行替换
    let updateFn = this.updater['textUpdater']
    let value = this.getTextVal(vm, str)
    str.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
      // 解析时遇到模板中需要替换为数据值的变量时，应添加一个观察者
      // 当变量重新赋值时，调用更新值节点到Dom的方法
      // new（实例化）后将调用observe.js中get方法
      let key = arguments[1].replace(/ /g, '')
      new Watcher(vm, key, () => {
        updateFn && updateFn(node, this.getTextVal(vm, str))
      }) // 通过观察者进行赋值
    })
    updateFn && updateFn(node, value)
  },
  setValue(vm, expr, value) { // [message,a]给文本赋值
    expr = expr.split('.')// 将对象先拆开成数组
    // 收敛
    return expr.reduce((prev, next, currentIndex) => {
      // 如果到对象最后一项时则开始赋值，如message:{a:1}将拆开成message.a = 1
      if (currentIndex === expr.length - 1) {
        return prev[next] = value
      }
      return prev[next]
    }, vm.$data)
  },
  getVal(vm, expr) {
    expr = expr.split('.')
    return expr.reduce((prev, next) => { // vm.$data.a.b
      return prev[next]
    }, vm.$data)
  },
  getTextVal(vm, expr) { // 获取编译后的文本内容
    return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
      return this.getVal(vm, arguments[1].replace(/ /g, ''))
    })
  },
  updater: {
    // 更新文本
    textUpdater(node, value) {
      node.textContent = value
    },
    // 更新输入框的值
    modelUpdater(node, value) {
      node.value = value
    }
  }
}